/*
 * NativeQueryIDL - NativeQuery Interface Definition Language
 * Copyright (C) 2010  Grzegorz Bugaj
 * http://www.gregbugaj.com
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.gregbugaj.nativequeryidl;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import com.gregbugaj.nativequeryidl.converters.ConverterRegistry;

/**
 * Reflection utility
 * @author devil
 *
 */
public final class ReflectUtil {

	/**
	 * Prevent instantiation of the class
	 */
	private ReflectUtil(){

	}

	/**
	 * Try to make a field accessible 
	 * @param field to make accessible
	 * @return true if the field is accessible or if it have been made accessible
	 */
	public static boolean makeAccessible(Field field) {		
		if(!field.isAccessible()){
			try{
				field.setAccessible(true);				
			}catch(SecurityException sec){						
				return false;				
			}
		}		
		return true;
	}

	/**
	 * Get single field {@link MetaDefinition}
	 * @param field
	 * @return
	 * @throws Exception
	 */
	private static MetaDefinition getMetaDefinition(Field field) throws Exception{
		Class<?> declaringClass=field.getDeclaringClass().getDeclaringClass();
		Method[] methods=declaringClass.getMethods();
		Method getMethod = null;
		//we need to replate all '_' with nothing '' ex ACTIVE_ACCOUNT becomes ACTIVEACCOUNT		
		String fieldName = field.getName().replace("_", "");
		
		for(Method m:methods){
			if(m.getName().equalsIgnoreCase("get"+fieldName) ||
					m.getName().equalsIgnoreCase("is"+fieldName)){
				getMethod=m;
				break;
			}
		}
		int dataIndex=-1;
		if(makeAccessible(field)){
			try {
				dataIndex=field.getInt(null);
			} catch (Exception e) {				
				throw new Exception("Unable to get value for field "+fieldName);
			}
		}
		MetaDefinition metaDefiniton= new MetaDefinition();
		metaDefiniton.setDataIndex(dataIndex);
		metaDefiniton.setPropertyField(getPropertyField(getMethod));
		metaDefiniton.setConverter(ConverterRegistry.lookup(getMethod.getReturnType()));
		return metaDefiniton;
	}


	/**
	 * Get all {@link MetaDefinition} for given class
	 * @param meta
	 * @return
	 * @throws Exception
	 */
	public static List<MetaDefinition> getMetaDefinitions(Class<?> meta) throws Exception {
		List<MetaDefinition> result=new ArrayList<MetaDefinition>();
		Field[] fields = meta.getDeclaredFields();	
		//Meta field have to be public static final and not synthetic
		for(Field f : fields){
			if(f.isSynthetic() == false
					&& ((f.getModifiers() & Modifier.STATIC) != 0)
					&& ((f.getModifiers() & Modifier.FINAL) != 0)
					&& ((f.getModifiers() & Modifier.PUBLIC) != 0)
			){
				result.add(getMetaDefinition(f));
			}
		}
		return result;
	}


	/**
	 * get the name of the Java Bean property for the given method.
	 * 
	 * @param method the method
	 * 
	 * @return the name of the Java Bean property, or null if the given method is not a Java Bean getter. 
	 * @throws Exception 
	 */
	public static Field getPropertyField(Method method) throws Exception {
		if (method == null) {
			throw new Exception("Method can't be null");
		}
		String methodName = method.getName();
		String declaredField="";
		if(methodName.startsWith("get")){
			declaredField=methodName.substring(3);
		}else if(methodName.startsWith("is")){
			declaredField=methodName.substring(2);
		}
		//First letter need to be converted to lowercase
		declaredField=declaredField.substring(0,1).toLowerCase()+declaredField.substring(1, declaredField.length());
		
		Class<?> declaringClass=method.getDeclaringClass();
		Field field = null;
		try {		
			field = declaringClass.getDeclaredField(declaredField);
		} catch(Exception ex){
			throw new Exception(ex);
		}
		return field;
	}

	/**
	 * Convenience method for setting value on a field
	 * @param field to set value on
	 * @param obj for the value
	 * @param value to set
	 * @throws Exception 
	 */
	public static void set(Field field, Object obj, Object value) throws Exception{
		if(makeAccessible(field)){
			try{				
				field.set(obj, value);
			}catch(Exception ex){
				throw new Exception(ex);
			}
		}
	}

}
